#define _J_VRIJEME_C_
#include "vrijeme.h"

#include "red.h"
#include "dretve.h"
#include "ispis.h"
#include "razno.h"
#include "prekidi.h"
#include "liste.h"
#include <arh/vrijeme.h>

//struktura podataka

//virtualno vrijeme sustava, pocetno 0:0, azurira se (barem) na svaki prekid
static vrijeme_t j_sat;

//zadnja ucitana vrijednost u brojilo (salje se arh sloju)
static arh_vrijeme_t j_sat_brojilo;

//staticki alocirani elementi za sve alarme
ZAUZMI(static j_alarm_t, j_alarm, NAJ_ALARMA);

//lista aktivnih alarma - slozena prema vremenima aktivacije
static j_alarmi_t j_alarmi;
//u buducnosti neka druga struktura radi brzeg pretrazivanje (neko stablo)
//problem je ubacivanje alarma u listu

//! pocetna inicijalizacija satnog podsustava
void j_sat_inicijaliziraj ()
{
	int prekidi_prije;
	static bool inicijalizirano = NEISTINA;
	
	if (inicijalizirano) {
		j_ispisi("Podsustav 'jezgra/vrijeme' je vec inicijaliziran!\n");
		return ;
	}
	inicijalizirano = ISTINA;

	//zabrani prekide (ako vec nisu), i pamti prethodno stanje
	prekidi_prije = j_dozvoli_prekide(NEISTINA);


	//resetiraj sat sustava
	j_sat.sekunde = j_sat.nsekunde = 0;
	
	//lista alarma je u pocetku prazna
	j_alarmi.prvi = j_alarmi.zadnji = NULL;

	memset(j_alarm, 0, sizeof(j_alarm));//brise cijelo polje

	//postavi prekidnu funkciju za prekid sata
	(void) j_prekidi_postavi(PREKIDI_SAT, j_sat_prekid);

	j_sat_brojilo = ARH_SAT_NAJVECI_BROJ;
	arh_sat_ucitaj_brojilo(j_sat_brojilo);
	arh_dozvoli_prekid_sata();


	//dozvoli prekide, ako su i prije bili dozvoljeni
	j_dozvoli_prekide(prekidi_prije);
}

//! na svaki prekid sustavskog sata poziva se ova f-ja (ako se prekidi prihv.)
static void j_sat_prekid (int param, void *kontekst)
{
	j_alarm_t *alarm;

	//j_ispisi("Prekid sata (%d.%d)\n", j_sat.sekunde, j_sat.nsekunde);
	//isteklo "j_sat_brojilo" - dodaj proteklo vrijeme u jezgrin sat
	j_sat = j_vrijeme_zbroji(j_sat, arh_vrijeme_u_j_vrijeme(j_sat_brojilo));

	while (j_alarmi.prvi) { //ima definiranih alarma?
		if (j_vrijeme_usporedi(j_sat, j_alarmi.prvi->budjenje) >= 0)
		{
			alarm = UZMI_PRVI_ELEMENT(&j_alarmi);
			
			if (alarm->zast & PERIODICKI) {
				alarm->budjenje = j_vrijeme_zbroji(
							alarm->period,
							alarm->budjenje);
				
				//stavi nazad u listu na odgovarajuce mjesto
				DODAJ_U_SORT_LISTU(&j_alarmi, alarm,
							j_alarm_usporedi)
			}

			//pozovi zadanu funkciju - stvori novu dretvu koja ce to
			if (alarm->obrada != NULL && !(alarm->zast&ALARMJEZGRE))
			{
				struct {
					void (*poc_funkc)(void *);
					void *param;
					void *stog;
					int prio;
					int rasp;
				} param;

				param.poc_funkc = alarm->obrada;
				param.param = alarm->param;

				param.stog = NULL;
				param.prio = j_dohvati_prioritet(
						&alarm->id_dretve);
				param.rasp = j_dohvati_nacin_rasporedjivanja(
						&alarm->id_dretve);

				j_stvori_dretvu_i ( &param,
						    j_opdr_id_procesa (
						    j_dohvati_opisnik (
						    alarm->id_dretve ) ) );
			}
			else if (alarm->obrada != NULL) {
				//jezgrin alarm
				alarm->obrada(alarm->param);
			}
			
			//mozda neko ceka, oslobodi sve (obrisi red)
			if (alarm->red != -1) {
				j_obrisi_red(alarm->red);
				alarm->red = -1;
			}

			if ((alarm->zast & OSLOBODIDR) && alarm->param != NULL){
				//makni dretvu iz njenog reda (i obrisi alarm)
				j_odblokiraj_dretvu (alarm->param);
			}
			
			j_alarm_azuriraj_sat();
			//pozvana funkcija moze potrajat, pa treba opet ocitat
			//i azurirat vrijeme
		}
		else {
			break;	//nije jos vrijeme alarma, a posto se gleda prvi
		}		//u listi, ostali su sigurno kasnije
	}
}

//azuriraj vrijeme jezgrina sata
static void j_alarm_azuriraj_sat ()
{
	arh_vrijeme_t preostalo, proteklo;
	vrijeme_t iduce;
	
	//dohvati sat iz brojila (sklopovskog)
	preostalo =  arh_sat_procitaj_brojilo();
	proteklo = arh_vrijeme_oduzmi(j_sat_brojilo, preostalo);

	if (proteklo < 0) { //ovo se ne smije dogoditi - ako se ipak dogodi ...
		j_ispisi("preostalo:%d\n", preostalo);
		j_ispisi("proteklo:%d\n", proteklo);
		j_zaustavi_potpuno("Greska u brojilima!");
	}

	//dodaj proteklo vrijeme u jezgrin sat
	j_sat = j_vrijeme_zbroji(j_sat, arh_vrijeme_u_j_vrijeme(proteklo));
	j_sat_brojilo = ARH_SAT_NAJVECI_BROJ;

	if (j_alarmi.prvi != NULL) { //ima li definiranih alarma?
		//ako se treba buditi prije prekida - azuriraj brojilo
		iduce = j_vrijeme_zbroji(j_sat,
				arh_vrijeme_u_j_vrijeme(j_sat_brojilo));

		if (j_vrijeme_usporedi(j_alarmi.prvi->budjenje, iduce) < 0) {
			iduce = j_vrijeme_oduzmi(j_alarmi.prvi->budjenje,j_sat);

			j_sat_brojilo = arh_j_vrijeme_u_arh_vrijeme(iduce);

			//najmanji broj za sat (njegovo brojilo) je definirano!
			if (j_sat_brojilo < ARH_SAT_PRAG)
				j_sat_brojilo = ARH_SAT_PRAG;
		}
	}

	arh_sat_ucitaj_brojilo(j_sat_brojilo);
}

//brisi postojeci alarm
int j_alarm_obrisi (void *param)
{
	int id, *idp = param;

	PARAMNULL(idp);
	id = *idp;

	if (OBRISI(j_alarm, id) == -1) {
		j_ispisi("Krivi id alarma ili nije ni bio zauzet (%d)\n", id);
		return -1;
	}

	//makni ga iz liste aktivnih ako je tu - ne mora biti
	MAKNI_IZ_LISTE(&j_alarmi, &j_alarm[id]);
	
	if (j_alarm[id].red != -1) {
		j_obrisi_red(j_alarm[id].red);
		j_alarm[id].red = -1;
	}
	
	return 0;
}

//! stvaranje novog alarma
int j_alarm_dohvati (void *param)
{
	alarm_t *p = param;
	int id;
	
	PARAMNULL(p);

	//rezerviranje novog alarma
	id = DOHVATI(j_alarm);
	
	if (id >= 0) {
		j_alarm[id].red = -1;
	}
	else {
		j_ispisi("nema slobodnog alarma\n");
		return -1;
	}

	return j_alarm_postavi_i (p, id, ISTINA);
}

//! modifikacija parametara postojeceg alarma
int j_alarm_postavi (void *param)
{
	alarm_t *p = param;
	int id;
	
	PARAMNULL(p);
	
	id = p->id;

	if (id < 0 || id >= NAJ_ALARMA || PROVJERI_SLOBODNO(j_alarm, id))
	{
		j_ispisi("Krivi id alarma\n");
		return -1;
	}

	return j_alarm_postavi_i (p, id, NEISTINA);
}

//! postavljanje parametara alarma (ili modifikacija postojeceg!)
int j_alarm_postavi_i (alarm_t *p, int id, bool novi)
{
	j_alarm_t *alarm;

	alarm = &j_alarm[id];

	alarm->id_dretve = j_id_dretve(NULL);

	if (novi || p->zast)
		alarm->zast = p->zast;

	if (novi || p->param)
		alarm->param = p->param;

	if (novi || p->obrada)
		alarm->obrada = p->obrada;

	if (novi)
		alarm->iduci = NULL;

	if (novi || p->kada.sekunde + p->kada.nsekunde > 0) {
		alarm->period = p->kada;

		j_alarm_azuriraj_sat();

		if (p->zast & ABSVRIJEME)
			alarm->budjenje = p->kada;
		else //RELVRIJEME
			alarm->budjenje = j_vrijeme_zbroji(j_sat, p->kada);
		
		//izbaci ga iz liste, ako se radi o postojecem alarmu kojeg se
		//modificira - mijenja vremena ili nesto drugo
		if (!novi)
			MAKNI_IZ_LISTE(&j_alarmi, alarm);

		//dodaj alarm u listu
		DODAJ_U_SORT_LISTU(&j_alarmi, alarm, j_alarm_usporedi)

		if (j_alarmi.prvi == alarm)	
			j_alarm_azuriraj_sat();
			//ovo je sad prvi alarm, mozda treba reinicijalizirat
			//brojilo u satnom mehanizmu arh sloja
	}

	if (p->zast & BLOKIRAJDR) { //stavi dretvu u red
		if (alarm->red == -1) { //novi red
			alarm->red = j_dohvati_red(REDALARMA, SORT_PRP, 0);
		}

		if (alarm->red != -1) {
			j_stavi_u_red(alarm->red, NULL, NULL);
		}
		else {	//ne moze se blokirat dretvu, obrisi i alarm
			//poruka o gresci je vec napisana - "nema reda"

			j_alarm_obrisi(&id);

			return -1;
		}
	}
	else {
		alarm->red = -1;
	}

	return id;
}

int j_dohvati_trenutni_sat (void *param)
{
	vrijeme_t *v = param;
	arh_vrijeme_t preostalo, proteklo;
	
	PARAMNULL(v);
	
	preostalo =  arh_sat_procitaj_brojilo();
	proteklo = arh_vrijeme_oduzmi(j_sat_brojilo, preostalo);

	if (proteklo < 0) { //ovo se ne smije dogoditi - ako se ipak dogodi ...
		j_ispisi("preostalo:%d\n", preostalo);
		j_ispisi("proteklo:%d\n", proteklo);
		j_zaustavi_potpuno("Greska u brojilima!");
	}

	//dodaj proteklo vrijeme na jezgrin sat
	*v = j_vrijeme_zbroji(j_sat, arh_vrijeme_u_j_vrijeme(proteklo));
	
	return 0;
}

/* usporedba alarma: vraca 0 ako je x == y, -1 za x < y, 1 ako je x > y */
static int j_alarm_usporedi (j_alarm_t *x, j_alarm_t *y)
{
	if (!x && !y)
		return 0;
	if (!x)
		return 1;
	if (!y)
		return -1;

	return j_vrijeme_usporedi(x->budjenje, y->budjenje);
}
